package com.letoken.platform.repo.web3.util;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.letoken.platform.pub.enums.LetokenApiResponseEnum;
import com.letoken.platform.pub.exception.ApiServiceException;
import com.letoken.platform.pub.res.web3.BscBalance;
import com.letoken.platform.pub.res.web3.BscResponse;
import com.letoken.platform.pub.res.web3.BscTransaction;
import com.letoken.platform.pub.res.token.TokenErc20TxRes;
import com.letoken.platform.pub.res.token.TokenTxRes;
import com.letoken.platform.pub.util.DateUtil;
import com.letoken.platform.pub.util.HttpClientUtil;
import com.letoken.platform.repo.wallet.po.Web3ChainProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.springframework.stereotype.Component;
import org.web3j.crypto.*;
import org.web3j.utils.Numeric;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.*;

/**
 * @author letokenlabs
 * @date 2022/11/16$
 */
@Slf4j
@Component
public class EvmScanUtil {

    public static final BigDecimal ETH_DECIMALS = new BigDecimal(1_000_000_000_000_000_000L);

    public static final BigInteger ETH_GAS_LIMIT = new BigInteger("100000");

    /**
     * 校验EVM地址
     * @param input
     * @return
     */
    public static boolean checkAddressChecksum(String input) {
        if (StrUtil.isEmpty(input) || !input.startsWith("0x"))
            return false;
        return isValidAddress(input);
    }

    public static boolean isValidAddress(String input) {
        String cleanInput = Numeric.cleanHexPrefix(input);

        try {
            Numeric.toBigIntNoPrefix(cleanInput);
        } catch (NumberFormatException e) {
            return false;
        }

        return cleanInput.length() == 40;
    }

    private static HttpHost getProxy(Web3ChainProvider provider) {
        return provider.getProxy() ? HttpHost.create(provider.getProxyUrl()) : null;
    }

    /**
     *  获取最新区块高度
     * @param provider Web3ChainProvider 网络名称或ID(bsc/bsctestnet,56/97)
     * @return
     */
    public static long blockHeight(Web3ChainProvider provider) {
        HashMap<String, String> parame = new HashMap<>();
        parame.put("module", "block");
        parame.put("action", "getblocknobytime");
        parame.put("timestamp", DateUtil.getUnixTimestamp());
        parame.put("closest", "before");
        parame.put("apikey", provider.getApiKey());
        String res = null;
        try {
            res = HttpClientUtil.sendGet(provider.getApiUrl(), parame, null, getProxy(provider));
        } catch (Exception e) {
            throw new ApiServiceException(LetokenApiResponseEnum.ERROR_WALLET_TX_001);
        }
        BscResponse bscResponse = JSONObject.parseObject(res, BscResponse.class);
        if (bscResponse.getStatus().equals("1")) {
            return Long.parseLong(bscResponse.getResult());
        } else {
            throw new ApiServiceException(bscResponse.getMessage());
        }
    }

    /**
     * 根据交易hash获取交易状态
     * @param provider Web3ChainProvider 网络名称或ID(bsc/bsctestnet,56/97)
     * @return
     */
    public static String getTxReceiptstatus(String txhash, Web3ChainProvider provider) {
        Boolean flag = false;
        HashMap<String, String> parame = new HashMap<>();
        parame.put("module", "transaction");
        parame.put("action", "gettxreceiptstatus");
        parame.put("txhash", txhash);
        parame.put("apikey", provider.getApiKey());
        String res = null;
        try {
            res = HttpClientUtil.sendGet(provider.getApiUrl(), parame, null, getProxy(provider));
        } catch (Exception e) {
            throw new ApiServiceException(LetokenApiResponseEnum.ERROR_WALLET_TX_001);
        }
        BscResponse bscResponse = JSONObject.parseObject(res, BscResponse.class);
        if (bscResponse.getStatus().equals("1")) {
            JSONObject result = JSONObject.parseObject(bscResponse.getResult());
            return result.get("status").toString();
        } else {
            throw new ApiServiceException(bscResponse.getMessage());
        }
    }

    /**
     * 获取单个地址的ETH余额
     *
     * @param address 钱包地址
     * @param provider Web3ChainProvider 网络名称或ID(bsc/bsctestnet,56/97)
     * @return wei
     */
    public static String getBalance(String address, Web3ChainProvider provider) {
        HashMap<String, String> parame = new HashMap<>();
        parame.put("module", "account");
        parame.put("action", "balance");
        parame.put("address", address);
        parame.put("apikey", provider.getApiKey());
        String res = null;
        try {
            res = HttpClientUtil.sendGet(provider.getApiUrl(), parame, null, getProxy(provider));
        } catch (Exception e) {
            throw new ApiServiceException(LetokenApiResponseEnum.ERROR_WALLET_TX_001);
        }
        BscResponse bscResponse = JSONObject.parseObject(res, BscResponse.class);
        if (bscResponse.getStatus().equals("1")) {
            return bscResponse.getResult();
        } else {
            throw new ApiServiceException(bscResponse.getMessage());
        }
    }


    /**
     * 获取多个地址的ETH余额
     * @param addressList 钱包地址(逗号分隔)
     * @param provider Web3ChainProvider 网络名称或ID(bsc/bsctestnet,56/97)
     * @return
     */
    public static List<BscBalance> getBalanceMulti(String addressList, Web3ChainProvider provider) {
        HashMap<String, String> parame = new HashMap<>();
        parame.put("module", "account");
        parame.put("action", "balancemulti");
        parame.put("address", addressList);
        parame.put("tag", "latest");
        parame.put("apikey", provider.getApiKey());
        String res = null;
        try {
            res = HttpClientUtil.sendGet(provider.getApiUrl(), parame, null, getProxy(provider));
        } catch (Exception e) {
            throw new ApiServiceException(LetokenApiResponseEnum.ERROR_WALLET_TX_001);
        }
        BscResponse bscResponse = JSONObject.parseObject(res, BscResponse.class);
        List<BscBalance> bscBalances = null;
        if (bscResponse.getStatus().equals("1")) {
            bscBalances = JSONObject.parseObject(bscResponse.getResult(), List.class);
        } else {
            throw new ApiServiceException(bscResponse.getMessage());
        }
        return bscBalances;
    }

    /**
     * 获取钱包代币余额（erc20)
     * @param tokenAddress 代币合约地址
     * @param address 用户钱包地址
     * @return wei
     */
    public static String getTokenBalance(String tokenAddress, String address, Web3ChainProvider provider) {
        HashMap<String, String> parame = new HashMap<>();
        parame.put("module", "account");
        parame.put("action", "tokenbalance");
        parame.put("contractaddress", tokenAddress);
        parame.put("address", address);
        parame.put("tag", "latest");
        parame.put("apikey", provider.getApiKey());
        String res = null;
        try {
            res = HttpClientUtil.sendGet(provider.getApiUrl(), parame, null, getProxy(provider));
        } catch (Exception e) {
            throw new ApiServiceException(LetokenApiResponseEnum.ERROR_WALLET_TX_001);
        }
        BscResponse bscResponse = JSONObject.parseObject(res, BscResponse.class);
        if (bscResponse.getStatus().equals("1")) {
            return bscResponse.getResult();
        } else {
            throw new ApiServiceException(bscResponse.getMessage());
        }
    }

    /**
     * 获取钱包Hash Internal记录
     * @param txhash txhash
     * @param provider Web3ChainProvider
     * @return wei
     */
    public static List<BscTransaction> getTxListInternal(String txhash, Web3ChainProvider provider) {
        Map<String, String> parame = new HashMap<>();
        parame.put("module", "account");
        parame.put("action", "txlistinternal");
        parame.put("txhash", txhash);
        parame.put("apikey", provider.getApiKey());
        String res = null;
        try {
            res = HttpClientUtil.sendGet(provider.getApiUrl(), parame, null, getProxy(provider));
        } catch (Exception e) {
            throw new ApiServiceException(LetokenApiResponseEnum.ERROR_WALLET_TX_001);
        }
        BscResponse bscResponse = JSONObject.parseObject(res, BscResponse.class);
        List<BscTransaction> bscTransaction = null;
        if (bscResponse.getStatus().equals("1")) {
            bscTransaction = JSONObject.parseObject(bscResponse.getResult(), List.class);
        } else {
            throw new ApiServiceException(bscResponse.getMessage());
        }
        return bscTransaction;
    }

    /**
     * 获取GasPrice
     * @param provider 网络名称或ID(bsc/bsctestnet,56/97)
     * @return wei
     */
    public static BigInteger getGasPrice(Web3ChainProvider provider) {
        HashMap<String, String> parame = new HashMap<>();
        parame.put("module", "gastracker");
        parame.put("action", "gasoracle");
        parame.put("apikey", provider.getApiKey());
        String res = null;
        try {
            res = HttpClientUtil.sendGet(provider.getApiUrl(), parame, null, getProxy(provider));
        } catch (Exception e) {
            throw new ApiServiceException(LetokenApiResponseEnum.ERROR_WALLET_TX_001);
        }
        BscResponse bscResponse = JSONObject.parseObject(res, BscResponse.class);
        if (bscResponse.getStatus().equals("1")) {
            JSONObject result = JSONObject.parseObject(bscResponse.getResult());
            return new BigInteger(result.get("ProposeGasPrice").toString()).multiply(BigInteger.valueOf(10).pow(9));
        } else {
            throw new ApiServiceException(bscResponse.getMessage());
        }
    }

    /**
     * 根据私钥获取地址
     *
     * @param privateKey
     * @return
     */
    public static String getAddressByPrivateKey(String privateKey) {
        ECKeyPair ecKeyPair = ECKeyPair.create(new BigInteger(privateKey, 16));
        return "0x" + Keys.getAddress(ecKeyPair).toLowerCase();
    }

    /**
     * 转换成最小单位 Wei
     *
     * @param ethAmount
     * @return
     */
    public static BigInteger toWei(BigDecimal ethAmount) {
        return ethAmount.multiply(ETH_DECIMALS).toBigInteger();
    }

    /**
     * 转换成最小单位 Wei
     *
     * @param ethAmount
     * @return
     */
    public static BigDecimal toWei2(BigDecimal ethAmount) {
        return ethAmount.multiply(ETH_DECIMALS);
    }

    /**
     * wei to eth
     *
     * @param wei
     * @return
     */
    public static BigDecimal toEth(BigInteger wei) {
        return new BigDecimal(wei).divide(ETH_DECIMALS, 18, RoundingMode.DOWN);
    }

    public static BigDecimal toEthScale(BigInteger wei, int scale) {
        return new BigDecimal(wei).divide(ETH_DECIMALS, scale, RoundingMode.DOWN);
    }


    public static List<TokenErc20TxRes> getTokenErc20Txs(Web3ChainProvider provider, String contractAddress, String walletAddress, Integer page, Integer pageSize, String sort) {
        Map<String, String> parame = new HashMap<>();
        parame.put("module", "account");
        parame.put("action", "tokentx");
        parame.put("contractaddress", contractAddress);
        parame.put("address", walletAddress);
        parame.put("page", page.toString());
        parame.put("offset", pageSize.toString());
        parame.put("sort", sort);
        parame.put("apikey", provider.getApiKey());
        String res = null;
        try {
            res = HttpClientUtil.sendGet(provider.getApiUrl(), parame, null, getProxy(provider));
        } catch (Exception e) {
            log.error(String.format("getTokenErc20Txs error >> walletAddress:%s error:%s", walletAddress, e.getMessage()));
            throw new ApiServiceException(LetokenApiResponseEnum.ERROR_WALLET_TX_001);
        }
        BscResponse bscResponse = JSONObject.parseObject(res, BscResponse.class);
        List<TokenErc20TxRes> list = null;
        if (bscResponse.getStatus().equals("1")) {
            list = JSONObject.parseArray(bscResponse.getResult(), TokenErc20TxRes.class);
        } else {
            throw new ApiServiceException(bscResponse.getMessage());
        }
        return list;
    }

    public static List<TokenTxRes> getTokenTxs(Web3ChainProvider provider, String walletAddress, Integer page, Integer pageSize, String sort) {
        Map<String, String> parame = new HashMap<>();
        parame.put("module", "account");
        parame.put("action", "txlist");
        parame.put("address", walletAddress);
        parame.put("page", page.toString());
        parame.put("offset", pageSize.toString());
        parame.put("sort", sort);
        parame.put("apikey", provider.getApiKey());
        String res = null;
        try {
            res = HttpClientUtil.sendGet(provider.getApiUrl(), parame, null, getProxy(provider));
        } catch (Exception e) {
            log.error(String.format("getTokenTxs error >> walletAddress:%s error:%s", walletAddress, e.getMessage()));
            throw new ApiServiceException(LetokenApiResponseEnum.ERROR_WALLET_TX_001);
        }
        BscResponse bscResponse = JSONObject.parseObject(res, BscResponse.class);
        List<TokenTxRes> list = null;
        if (bscResponse.getStatus().equals("1")) {
            list = JSONObject.parseArray(bscResponse.getResult(), TokenTxRes.class);
        } else {
            throw new ApiServiceException(bscResponse.getMessage());
        }
        return list;
    }
}
